﻿using UnityEngine;
using System.Collections.Generic;
using XLua;
using System;
using System.IO;

#if UNITY_EDITOR
using UnityEditor;
#endif

namespace GameCore
{
    [LuaCallCSharp]
    public partial class LuaBehaviour : MonoBehaviour
    {
#if UNITY_EDITOR
        public DefaultAsset luaScript;
#endif
        
        public string requirePath;
        
        public LuaTable LuaClass { get; protected set; }

        private static float lastGCTime;
        private const float GCInterval = 1;

        private LuaFunction luaOnEnable;
        private LuaFunction luaStart;
        private LuaFunction luaUpdate;
        private LuaFunction luaFixedUpdate;
        private LuaFunction luaOnDisable;
        private LuaFunction luaOnDestroy;

        private LuaFunction luaOnTriggerEnter2D;
        private LuaFunction luaOnTriggerStay2D;
        private LuaFunction luaOnTriggerExit2D;
        private LuaFunction luaOnCollisionEnter2D;
        private LuaFunction luaOnCollisionStay2D;
        private LuaFunction luaOnCollisionExit2D;
        
        private LuaFunction luaOnMouseDown;
        private LuaFunction luaOnMouseUp;
        private LuaFunction luaOnMouseEnter;
        private LuaFunction luaOnMouseExit;
        private LuaFunction luaOnMouseDrag;
        private LuaFunction luaOnMouseOver;

        [SerializeField] public List<ObjectWrap> objects = new List<ObjectWrap>();
        [SerializeField] public List<ValueWrap> values = new List<ValueWrap>();

        #region 初始化

        protected void Init(out LuaTable injections, out LuaFunction newFunc)
        {
            injections = ProjectLuaEnv.Instance.NewTable();
            
            foreach (var t in values)
            {
                injections.Set(t.name, JsonToValue(t.jsonStr, Type.GetType(t.typeName)));
            }
            
            foreach (var obj in objects)
            {
                injections.Set(obj.name, obj.obj);
            }
            
            injections.Set("host", this);
            injections.Set("gameObject", gameObject);
            injections.Set("transform", transform);
            
            string cmd = $"require('Core.Global'); local t = require('{requirePath}'); return t;";
            LuaTable table = (LuaTable)ProjectLuaEnv.Instance.DoString(cmd)[0];
            
            newFunc = table.Get<LuaFunction>("NewFromCS");
        }

        #endregion

        #region Unity生命周期

        protected virtual void Awake()
        {
            // 初始化数据
            Init(out LuaTable injections, out LuaFunction newFunc);
            
            // 初始化Lua类
            LuaClass = (LuaTable)newFunc.Call(injections)[0];
            LuaFunction luaAwake = LuaClass.Get<LuaFunction>("__init");
            luaAwake?.Call(LuaClass);

            LuaClass.Get("OnEnable", out luaOnEnable);
            LuaClass.Get("Start", out luaStart);
            LuaClass.Get("Update", out luaUpdate);
            LuaClass.Get("FixedUpdate", out luaFixedUpdate);
            LuaClass.Get("OnDisable", out luaOnDisable);
            LuaClass.Get("OnDestroy", out luaOnDestroy);
            
            LuaClass.Get("OnTriggerEnter2D", out luaOnTriggerEnter2D);
            LuaClass.Get("OnTriggerStay2D", out luaOnTriggerStay2D);
            LuaClass.Get("OnTriggerExit2D", out luaOnTriggerExit2D);
            LuaClass.Get("OnCollisionEnter2D", out luaOnCollisionEnter2D);
            LuaClass.Get("OnCollisionStay2D", out luaOnCollisionStay2D);
            LuaClass.Get("OnCollisionExit2D", out luaOnCollisionExit2D);
            
            LuaClass.Get("OnMouseDown"  , out luaOnMouseDown);
            LuaClass.Get("OnMouseUp"    , out luaOnMouseUp);
            LuaClass.Get("OnMouseEnter" , out luaOnMouseEnter);
            LuaClass.Get("OnMouseExit"  , out luaOnMouseExit);
            LuaClass.Get("OnMouseDrag"  , out luaOnMouseDrag);
            LuaClass.Get("OnMouseOver"  , out luaOnMouseOver);
        }
        
        protected void OnEnable()
        {
            luaOnEnable?.Call(LuaClass);
        }

        protected void Start()
        {
            luaStart?.Call(LuaClass);
        }
        
        protected void Update()
        {
            luaUpdate?.Call(LuaClass);
            if (Time.time - lastGCTime > GCInterval)
            {
                ProjectLuaEnv.Instance.Tick();
                lastGCTime = Time.time;
            }
        }

        protected void FixedUpdate()
        {
            luaFixedUpdate?.Call(LuaClass);
        }

        protected void OnDisable()
        {
            luaOnDisable?.Call(LuaClass);
        }

        protected void OnDestroy()
        {
            StopAllCoroutines();
            luaOnDestroy?.Call(LuaClass);

            luaOnDestroy = null;
            luaUpdate = null;
        }

        #endregion

        #region 碰撞和触发器

        protected void OnTriggerEnter2D(Collider2D other)
        {
            luaOnTriggerEnter2D?.Call(LuaClass, other);
        }
        
        protected void OnTriggerStay2D(Collider2D other)
        {
            luaOnTriggerStay2D?.Call(LuaClass, other);
        }

        protected void OnTriggerExit2D(Collider2D other)
        {
            luaOnTriggerExit2D?.Call(LuaClass, other);
        }

        protected void OnCollisionEnter2D(Collision2D other)
        {
            luaOnCollisionEnter2D?.Call(LuaClass, other);
        }

        protected void OnCollisionStay2D(Collision2D other)
        {
            luaOnCollisionStay2D?.Call(LuaClass, other);
        }
        
        protected void OnCollisionExit2D(Collision2D other)
        {
            luaOnCollisionExit2D?.Call(LuaClass, other);
        }

        #endregion

        #region 鼠标事件

        protected void OnMouseDown()
        {
            luaOnMouseDown?.Call(LuaClass);
        }
        
        protected void OnMouseUp()
        {
            luaOnMouseUp?.Call(LuaClass);
        }

        protected void OnMouseDrag()
        {
            luaOnMouseDrag?.Call(LuaClass);
        }

        protected void OnMouseEnter()
        {
            luaOnMouseEnter?.Call(LuaClass);
        }
        
        protected void OnMouseExit()
        {
            luaOnMouseExit?.Call(LuaClass);
        }
        
        protected void OnMouseOver()
        {
            luaOnMouseOver?.Call(LuaClass);
        }

        #endregion

        #region 数据Json化和反Json化

        public static string ValueToJson(object value)
        {
            Type type = value.GetType();
            Type wrapType = typeof(ObjWrap<>).MakeGenericType(type);
            var target = Activator.CreateInstance(wrapType);
            ((IObjWrap)target).SetObj(value);
            return JsonUtility.ToJson(target);
        }

        public static object JsonToValue(string json, Type type)
        {
            if (WRAP_TYPE_DICT.TryGetValue(type, out var tempWrapType))
            {
                var target = JsonUtility.FromJson(json, tempWrapType);
                return ((IObjWrap)target).GetObj();
            }
            else if (type.IsValueType())
            {
                Debug.LogError($"msut register {type.FullName} in wrapTypeDict, because it is value type!");
                return null;
            }
            else
            {
                Type wrapType = typeof(ObjWrap<>).MakeGenericType(type);
                WRAP_TYPE_DICT.Add(type, wrapType);
                var target = JsonUtility.FromJson(json, wrapType);
                return ((IObjWrap)target).GetObj();
            }
        }

        #endregion

        #region 持久化保存数据

        public virtual void Rebuild()
        {
#if UNITY_EDITOR
            if (transform.parent != null) return;
            string assetPath = PrefabUtility.GetPrefabAssetPathOfNearestInstanceRoot(gameObject);
            if (string.IsNullOrEmpty(assetPath)) return;
            if (File.Exists(assetPath))
            {
                File.Delete(assetPath);
            }
            if(File.Exists(assetPath + ".meta"))
            {
                File.Delete(assetPath + ".meta");
            }
            PrefabUtility.SaveAsPrefabAsset(gameObject, assetPath, out bool s);
#endif
        }

        #endregion
    }
}
